<!DOCTYPE html>
<html class="no-js" lang="zh">
  <head>
<meta charset="utf-8">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="alternate" href="https://www.desgard.com" hreflang="pt-BR">
<link href="http://gmpg.org/xfn/11" rel="profile">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="theme-color" content="#005344">
<title>Guardia · 瓜地</title>
<meta name="google-site-verification" content="zS1dSn20XtA4FJYEOQLXqI0boxZdMnJ2g3beje-cl20">
<meta name="description" content="I write many code to write less code.💻">
<meta name="keywords" content="">
<!-- Social: Facebook / Open Graph -->
<meta property="og:url" content="https://www.desgard.com/TableView-Reuse/">
<meta property="og:title" content="       复用的精妙 - UITableView 复用技术原理分析 | Gua  ">
<meta property="og:description" content="I write many code to write less code.💻">
<meta property="og:site_name" content="Desgard_Duan">
<meta property="og:locale" content="pt_BR">
<meta property="og:type" content="website">
<meta property="og:author" content="https://www.facebook.com/desgard.duan">
<meta property="og:image" content="https://www.desgard.com">
<!-- Social: Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@nandomoreirame">
<meta name="twitter:domain" content="https://www.desgard.com">
<meta name="twitter:title" content="       复用的精妙 - UITableView 复用技术原理分析 | Gua  ">
<meta name="twitter:description" content="I write many code to write less code.💻">
<meta name="twitter:image:src" content="https://www.desgard.com">
<!-- Favicons -->
<link rel="apple-touch-icon" sizes="114x114" href="https://www.desgard.com/assets/ico/apple-touch-icon-114-516f4e19976b9e4dbb77ad9b576831fe.png">
<link rel="apple-touch-icon" sizes="72x72" href="https://www.desgard.com/assets/ico/apple-touch-icon-72-5409b2df229305703caf583d86c845ab.png">
<link rel="apple-touch-icon" href="https://www.desgard.com/assets/ico/apple-touch-icon-57-aa873e019cf659e0d4e6a0b5bb9f379d.png">
<link rel="shortcut icon" href="https://www.desgard.com/assets/ico/favicon-4298be3d3fbe23e18d65bded9d930899.png">
<!-- rel prev and next -->
<!-- Canonical link tag -->
<link rel="canonical" href="https://www.desgard.com/TableView-Reuse/">
<link rel="alternate" type="application/rss+xml" title="Gua" href="https://www.desgard.com/feed.xml">
<link rel="stylesheet" href="/assets/main-0b7b828712f4c43b75bba4861c907bb1.css">
<script src="/assets/modernizr-custom-cb807611a3e262b2eac59444cbab74d6.js" data-cfasync="false"></script>
<script src="/assets/js/jquery-3.1.js"></script>
<script src="/assets/js/jquery.rotate.js"></script>
<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "Website",
  "publisher": "www.desgard.com",
  "url": "http://www.desgard.com/",
  "description": "瓜地"
}
</script>
<script type="text/javascript">
  var disqus_shortname = 'desgard',
      baseurl          = '';
</script>
<!--
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-52446115-1']);
_gaq.push(['_trackPageview']);
(function() {
  var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
  ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>-->
<script>
var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "//hm.baidu.com/hm.js?c5a8123bc51782a3083a631ed9ff50e4";
  var s = document.getElementsByTagName("script")[0]; 
  s.parentNode.insertBefore(hm, s);
})();
</script>
<script type="text/x-mathjax-config">
    MathJax.Hub.Config({
      tex2jax: {
        inlineMath: [ ['$','$'] ],
        displayMath: [ ['$$','$$'] ],
        processEscapes: true
      }
    });
  </script>
  </head>
  <body class="post-template single">
    <header class="header">
  <div class="container">
    <h1><a href="/"><strong>desgard</strong>.com</a></h1>
    <nav class="navbar">
      <ul>
        <li><a href="/">Home</a></li>
        <li><a href="#!" data-modal="modalSearch"><i class="fa fa-search"></i></a></li>
        <li><a href="/comment " target="_blank"><i class="fa fa-comments"></i></a></li>
        <li><a href="/feed.xml" target="_blank"><i class="fa fa-feed"></i></a></li>
      </ul>
    </nav>
  </div>
</header>
    <main class="wrapper container" itemprop="mainContentOfPage" itemscope="itemscope" itemtype="http://schema.org/Blog">
      <article class="post" itemscope="itemscope" itemtype="http://schema.org/BlogPosting" itemprop="blogPost">
  <header class="post-header">
    <h1>复用的精妙 - UITableView 复用技术原理分析</h1>
    <div class="post-meta">
      <time datetime="2015-10-13">
        <i class="fa fa-calendar-o"></i> <time datetime="2017-04-16"> 2017-04-16
      </time>
      <span>
        <i class="fa fa-comments"></i> <a href="#container">Comment me!</a>
      </span>
      <span>
<!--
        <i class="fa fa-folder-open-o"></i> 
-->
      </span>
    </div>
  </header>
  <blockquote>
  <p>本文截图来源于 <strong>爱奇艺电视果</strong> iOS 客户端。</p>
</blockquote>
<p>在现在很多公司的 app 中，许多展示页面为了多条数据内容，而采用 <code>UITableView</code> 来设计页面。在滑动 <code>UITableView</code> 的时候，并不会因为数据量大而产生卡顿的情况，这正是因为其<strong>复用机制</strong>的特点。但是其复用机制是如何实现的？我决定来探索一番。</p>
<h2 id="chameleon-project">Chameleon PROJECT</h2>
<p><a href="https://github.com/BigZaphod/Chameleon"><em>Chameleon</em></a> 是我长期以来一直关注的一个项目。接触过 macOS 开发的人肯定多少有写了解。（虽然这个项目在三年以前就已经停更，但是在原理上还是有很高的参考价值。）<em>Chameleon</em> 用于将 iOS 的功能迁移到 macOS 上，并且在其中为 macOS 实现了一套与 iOS UIKit 同名的框架，并且其代码都为开源。由于 <em>Chameleon</em> 属于对苹果早期源码的逆向工程项目，所以我们可以据此来对 iOS 一些闭源库展开学习和思路的借鉴。</p>
<blockquote>
  <p><em>Chameleon</em> 所迁移的 iOS 版本为 <code>3.2</code> ，如今已经没有人使用，所以其代码和思路我们只能用来了解。例如在 iOS 8 之后推出的根据 <code>autoLayout</code> 自动计算 <code>cell</code> 高度的功能，在其中无法体现。</p>
</blockquote>
<p><img src="http://7xwh85.com1.z0.glb.clouddn.com/14921391544358.jpg" alt="工程结构截图" /></p>
<h2 id="uitableview-">UITableView 的初始化方法</h2>
<p>当我们定义一个 <code>UITableView</code> 对象的时候，需要对这个对象进行初始化。最常用的方法莫过于 <code>- (id)initWithFrame:(CGRect)frame style:(UITableViewStyle)theStyle</code>。下面跟着这个初始化入口，逐渐来分析代码：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">-</span> <span class="p">(</span><span class="nb">id</span><span class="p">)</span><span class="ss">initWithFrame</span><span class="p">:(</span><span class="no">CGRect</span><span class="p">)</span><span class="n">frame</span> <span class="ss">style</span><span class="p">:(</span><span class="no">UITableViewStyle</span><span class="p">)</span><span class="n">theStyle</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">((</span><span class="nb">self</span><span class="o">=[</span><span class="k">super</span> <span class="ss">initWithFrame</span><span class="p">:</span><span class="n">frame</span><span class="o">]</span><span class="p">))</span> <span class="p">{</span>
        <span class="sr">//</span> <span class="err">确定</span> <span class="no">TableView</span> <span class="err">的</span> <span class="no">Style</span>
        <span class="n">_style</span> <span class="o">=</span> <span class="n">theStyle</span><span class="p">;</span>
        <span class="sr">//</span> <span class="err">要点一：</span><span class="no">Cell</span> <span class="err">缓存字典</span>
        <span class="n">_cachedCells</span> <span class="o">=</span> <span class="o">[[</span><span class="no">NSMutableDictionary</span> <span class="n">alloc</span><span class="o">]</span> <span class="n">init</span><span class="o">]</span><span class="p">;</span>
        <span class="sr">//</span> <span class="err">要点二：</span><span class="no">Section</span> <span class="err">缓存</span> <span class="no">Mutable</span> <span class="nb">Array</span>
        <span class="n">_sections</span> <span class="o">=</span> <span class="o">[[</span><span class="no">NSMutableArray</span> <span class="n">alloc</span><span class="o">]</span> <span class="n">init</span><span class="o">]</span><span class="p">;</span>
        <span class="sr">//</span> <span class="err">要点三：复用</span> <span class="no">Cell</span> <span class="no">Mutable</span> <span class="no">Set</span>
        <span class="n">_reusableCells</span> <span class="o">=</span> <span class="o">[[</span><span class="no">NSMutableSet</span> <span class="n">alloc</span><span class="o">]</span> <span class="n">init</span><span class="o">]</span><span class="p">;</span>
        <span class="sr">//</span> <span class="err">一些关于</span> <span class="no">Table</span> <span class="no">View</span> <span class="err">的属性设置</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">separatorColor</span> <span class="o">=</span> <span class="o">[</span><span class="no">UIColor</span> <span class="ss">colorWithRed</span><span class="p">:</span><span class="o">.</span><span class="mi">88</span><span class="n">f</span> <span class="ss">green</span><span class="p">:</span><span class="o">.</span><span class="mi">88</span><span class="n">f</span> <span class="ss">blue</span><span class="p">:</span><span class="o">.</span><span class="mi">88</span><span class="n">f</span> <span class="ss">alpha</span><span class="p">:</span><span class="mi">1</span><span class="o">]</span><span class="p">;</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">separatorStyle</span> <span class="o">=</span> <span class="no">UITableViewCellSeparatorStyleSingleLine</span><span class="p">;</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">showsHorizontalScrollIndicator</span> <span class="o">=</span> <span class="no">NO</span><span class="p">;</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">allowsSelection</span> <span class="o">=</span> <span class="no">YES</span><span class="p">;</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">allowsSelectionDuringEditing</span> <span class="o">=</span> <span class="no">NO</span><span class="p">;</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">sectionHeaderHeight</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">sectionFooterHeight</span> <span class="o">=</span> <span class="mi">22</span><span class="p">;</span>
        <span class="nb">self</span><span class="o">.</span><span class="n">alwaysBounceVertical</span> <span class="o">=</span> <span class="no">YES</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">_style</span> <span class="o">==</span> <span class="no">UITableViewStylePlain</span><span class="p">)</span> <span class="p">{</span>
            <span class="nb">self</span><span class="o">.</span><span class="n">backgroundColor</span> <span class="o">=</span> <span class="o">[</span><span class="no">UIColor</span> <span class="n">whiteColor</span><span class="o">]</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="sr">//</span> <span class="err">加入</span> <span class="no">Layout</span> <span class="err">标记，进行手动触发布局设置</span>
        <span class="o">[</span><span class="nb">self</span> <span class="n">_setNeedsReload</span><span class="o">]</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">return</span> <span class="nb">self</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>在初始化代码中就看到了重点，<code>_cachedCells</code>、<code>_sections</code> 和 <code>_reusableCells</code> 无疑是复用的核心成员。</p>
<h2 id="section">代码跟踪</h2>
<p>我们先来查看一下 <code>_setNeedsReload</code> 方法中做了什么：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span><span class="n">_setNeedsReload</span> <span class="p">{</span>
    <span class="n">_needsReload</span> <span class="o">=</span> <span class="no">YES</span><span class="p">;</span>
    <span class="o">[</span><span class="nb">self</span> <span class="n">setNeedsLayout</span><span class="o">]</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>首先先对 <code>_needsReload</code> 进行标记，之后调用了 <code>setNeedsLayout</code> 方法。对于 <code>UIView</code> 的 <code>setNeedsLayout</code> 方法，在调用后 <em>Runloop</em> 会在即将到来的周期中来检测 <code>displayIfNeeded</code> 标记，如果为 <code>YES</code> 则会进行 <code>drawRect</code> 视图重绘。作为 Apple <em>UIKit</em> 层中的基础 Class，在属性变化后都会进行一次视图重绘的过程。这个属性过程的变化即为对象的初始化加载以及手势交互过程。这也就是官方文档中的 <a href="https://developer.apple.com/library/content/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/WindowsandViews/WindowsandViews.html#//apple_ref/doc/uid/TP40009503-CH2-SW42"><em>The Runtime Interaction Model</em></a>。</p>
<p><img src="http://7xwh85.com1.z0.glb.clouddn.com/11222.png" alt="布局方法载入流程" /></p>
<p>当 <em>Runloop</em> 到来时，开始重绘过程即调用 <code>layoutSubViews</code> 方法。在 <code>UITableView</code> 中这个方法已经被重写过：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span><span class="n">layoutSubviews</span> <span class="p">{</span>
    <span class="sr">//</span> <span class="err">会在初始化的末尾手动调用重绘过程</span>
    <span class="sr">//</span> <span class="err">并且</span> <span class="no">UITableView</span> <span class="err">是</span> <span class="no">UIScrollView</span> <span class="err">的继承，会接受手势</span>
    <span class="sr">//</span> <span class="err">所以在滑动</span> <span class="no">UITableView</span> <span class="err">的时候也会调用</span>
    <span class="n">_backgroundView</span><span class="o">.</span><span class="n">frame</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">bounds</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">根据标记确定是否执行数据更新操作</span>
    <span class="o">[</span><span class="nb">self</span> <span class="n">_reloadDataIfNeeded</span><span class="o">]</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">布局入口</span>
    <span class="o">[</span><span class="nb">self</span> <span class="n">_layoutTableView</span><span class="o">]</span><span class="p">;</span>
    <span class="o">[</span><span class="k">super</span> <span class="n">layoutSubviews</span><span class="o">]</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>接下来我们开始查看 <code>_reloadDataIfNeeded</code> 以及 <code>reloadData</code> 方法：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span><span class="n">_reloadDataIfNeeded</span> <span class="p">{</span>
    <span class="sr">//</span> <span class="err">查询</span> <span class="n">_needsReload</span> <span class="err">标记</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">_needsReload</span><span class="p">)</span> <span class="p">{</span>
        <span class="o">[</span><span class="nb">self</span> <span class="n">reloadData</span><span class="o">]</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
<span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span><span class="n">reloadData</span> <span class="p">{</span>
    <span class="sr">//</span> <span class="err">清除之前的缓存并删除</span> <span class="no">Cell</span>
    <span class="sr">//</span> <span class="n">makeObjectsPerformSelector</span> <span class="err">方法值都进行调用某个方法</span>
    <span class="o">[[</span><span class="n">_cachedCells</span> <span class="n">allValues</span><span class="o">]</span> <span class="ss">makeObjectsPerformSelector</span><span class="p">:</span><span class="vi">@selector</span><span class="p">(</span><span class="n">removeFromSuperview</span><span class="p">)</span><span class="o">]</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">复用</span> <span class="no">Cell</span> <span class="no">Set</span> <span class="err">也进行删除操作</span>
    <span class="o">[</span><span class="n">_reusableCells</span> <span class="ss">makeObjectsPerformSelector</span><span class="p">:</span><span class="vi">@selector</span><span class="p">(</span><span class="n">removeFromSuperview</span><span class="p">)</span><span class="o">]</span><span class="p">;</span>
    <span class="o">[</span><span class="n">_reusableCells</span> <span class="n">removeAllObjects</span><span class="o">]</span><span class="p">;</span>
    <span class="o">[</span><span class="n">_cachedCells</span> <span class="n">removeAllObjects</span><span class="o">]</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">删除选择的</span> <span class="no">Cell</span>
    <span class="n">_selectedRow</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">删除被高亮的</span> <span class="no">Cell</span>
    <span class="n">_highlightedRow</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">更新缓存中状态</span>
    <span class="o">[</span><span class="nb">self</span> <span class="n">_updateSectionsCache</span><span class="o">]</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">设置</span> <span class="no">Size</span>
    <span class="o">[</span><span class="nb">self</span> <span class="n">_setContentSize</span><span class="o">]</span><span class="p">;</span>
    <span class="n">_needsReload</span> <span class="o">=</span> <span class="no">NO</span><span class="p">;</span>
<span class="p">}</span></code></pre></div>
<p>当 <code>reloadData</code> 方法被触发时，<code>UITableView</code> 默认为在这个 <code>UITableView</code> 中的数据将会全部发生变化。测试之前遗留下的缓存列表以及复用列表全部都丧失了利用性。为了避免出现<strong>悬挂指针</strong>的情况（有可能某个 cell 被其他的视图进行了引用），我们需要对每个 cell 进行 <code>removeFromSuperview</code> 处理，这个处理即针对于容器 <code>UITableView</code>，又对其他的引用做出保障。然后我们更新当前 tableView 中的两个缓存容器，<code>_reusableCells</code> 和 <code>_cachedCells</code>，以及其他需要重置的成员属性。</p>
<p><img src="http://7xwh85.com1.z0.glb.clouddn.com/Mobile%20Portrait.png" alt="Mobile Portrait" /></p>
<p>最关键的地方到了，缓存状态的更新方法 <code>_updateSectionsCache</code>，其中涉及到数据如何存储、如何复用的操作：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span><span class="n">_updateSectionsCache</span> <span class="p">{</span>
    <span class="sr">//</span> <span class="err">使用</span> <span class="n">dataSource</span> <span class="err">来创建缓存容器</span>
    <span class="sr">//</span> <span class="err">如果没有</span> <span class="n">dataSource</span> <span class="err">则放弃重用操作</span>
    <span class="sr">//</span> <span class="err">在这个逆向工程中并没有对</span> <span class="n">header</span> <span class="err">进行缓存操作，但是</span> <span class="no">Apple</span> <span class="err">的</span> <span class="no">UIKit</span> <span class="err">中一定也做到了</span>
    <span class="sr">//</span> <span class="err">真正的</span> <span class="no">UIKit</span> <span class="err">中应该会获取更多的数据进行存储，并实现了</span> <span class="no">TableView</span> <span class="err">中所有视图的复用</span>
    <span class="sr">//</span> <span class="err">先移除每个</span> <span class="no">Section</span> <span class="err">的</span> <span class="no">Header</span> <span class="err">和</span> <span class="no">Footer</span> <span class="err">视图</span>
    <span class="k">for</span> <span class="p">(</span><span class="no">UITableViewSection</span> <span class="o">*</span><span class="n">previousSectionRecord</span> <span class="k">in</span> <span class="n">_sections</span><span class="p">)</span> <span class="p">{</span>
        <span class="o">[</span><span class="n">previousSectionRecord</span><span class="o">.</span><span class="n">headerView</span> <span class="n">removeFromSuperview</span><span class="o">]</span><span class="p">;</span>
        <span class="o">[</span><span class="n">previousSectionRecord</span><span class="o">.</span><span class="n">footerView</span> <span class="n">removeFromSuperview</span><span class="o">]</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="sr">//</span> <span class="err">清除旧缓存，对容器进行初始化操作</span>
    <span class="o">[</span><span class="n">_sections</span> <span class="n">removeAllObjects</span><span class="o">]</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">_dataSource</span><span class="p">)</span> <span class="p">{</span>
        <span class="sr">//</span> <span class="err">根据</span> <span class="n">dataSource</span> <span class="err">计算高度和偏移量</span>
        <span class="n">const</span> <span class="no">CGFloat</span> <span class="n">defaultRowHeight</span> <span class="o">=</span> <span class="n">_rowHeight</span> <span class="sc">?:</span> <span class="n">_UITableViewDefaultRowHeight</span><span class="p">;</span>
        <span class="sr">//</span> <span class="err">获取</span> <span class="no">Section</span> <span class="err">数目</span>
        <span class="n">const</span> <span class="no">NSInteger</span> <span class="n">numberOfSections</span> <span class="o">=</span> <span class="o">[</span><span class="nb">self</span> <span class="n">numberOfSections</span><span class="o">]</span><span class="p">;</span>
        <span class="k">for</span> <span class="p">(</span><span class="no">NSInteger</span> <span class="n">section</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span> <span class="n">section</span><span class="o">&lt;</span><span class="n">numberOfSections</span><span class="p">;</span> <span class="n">section</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">const</span> <span class="no">NSInteger</span> <span class="n">numberOfRowsInSection</span> <span class="o">=</span> <span class="o">[</span><span class="nb">self</span> <span class="ss">numberOfRowsInSection</span><span class="p">:</span><span class="n">section</span><span class="o">]</span><span class="p">;</span>
            <span class="no">UITableViewSection</span> <span class="o">*</span><span class="n">sectionRecord</span> <span class="o">=</span> <span class="o">[[</span><span class="no">UITableViewSection</span> <span class="n">alloc</span><span class="o">]</span> <span class="n">init</span><span class="o">]</span><span class="p">;</span>
            <span class="n">sectionRecord</span><span class="o">.</span><span class="n">headerTitle</span> <span class="o">=</span> <span class="n">_dataSourceHas</span><span class="o">.</span><span class="n">titleForHeaderInSection?</span> <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">dataSource</span> <span class="ss">tableView</span><span class="p">:</span><span class="nb">self</span> <span class="ss">titleForHeaderInSection</span><span class="p">:</span><span class="n">section</span><span class="o">]</span> <span class="p">:</span> <span class="kp">nil</span><span class="p">;</span>
            <span class="n">sectionRecord</span><span class="o">.</span><span class="n">footerTitle</span> <span class="o">=</span> <span class="n">_dataSourceHas</span><span class="o">.</span><span class="n">titleForFooterInSection?</span> <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">dataSource</span> <span class="ss">tableView</span><span class="p">:</span><span class="nb">self</span> <span class="ss">titleForFooterInSection</span><span class="p">:</span><span class="n">section</span><span class="o">]</span> <span class="p">:</span> <span class="kp">nil</span><span class="p">;</span>
            <span class="n">sectionRecord</span><span class="o">.</span><span class="n">headerHeight</span> <span class="o">=</span> <span class="n">_delegateHas</span><span class="o">.</span><span class="n">heightForHeaderInSection?</span> <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">delegate</span> <span class="ss">tableView</span><span class="p">:</span><span class="nb">self</span> <span class="ss">heightForHeaderInSection</span><span class="p">:</span><span class="n">section</span><span class="o">]</span> <span class="p">:</span> <span class="n">_sectionHeaderHeight</span><span class="p">;</span>
            <span class="n">sectionRecord</span><span class="o">.</span><span class="n">footerHeight</span> <span class="o">=</span> <span class="n">_delegateHas</span><span class="o">.</span><span class="n">heightForFooterInSection</span> <span class="p">?</span> <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">delegate</span> <span class="ss">tableView</span><span class="p">:</span><span class="nb">self</span> <span class="ss">heightForFooterInSection</span><span class="p">:</span><span class="n">section</span><span class="o">]</span> <span class="p">:</span> <span class="n">_sectionFooterHeight</span><span class="p">;</span>
            <span class="n">sectionRecord</span><span class="o">.</span><span class="n">headerView</span> <span class="o">=</span> <span class="p">(</span><span class="n">sectionRecord</span><span class="o">.</span><span class="n">headerHeight</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">_delegateHas</span><span class="o">.</span><span class="n">viewForHeaderInSection</span><span class="p">)?</span> <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">delegate</span> <span class="ss">tableView</span><span class="p">:</span><span class="nb">self</span> <span class="ss">viewForHeaderInSection</span><span class="p">:</span><span class="n">section</span><span class="o">]</span> <span class="p">:</span> <span class="kp">nil</span><span class="p">;</span>
            <span class="n">sectionRecord</span><span class="o">.</span><span class="n">footerView</span> <span class="o">=</span> <span class="p">(</span><span class="n">sectionRecord</span><span class="o">.</span><span class="n">footerHeight</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">_delegateHas</span><span class="o">.</span><span class="n">viewForFooterInSection</span><span class="p">)?</span> <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">delegate</span> <span class="ss">tableView</span><span class="p">:</span><span class="nb">self</span> <span class="ss">viewForFooterInSection</span><span class="p">:</span><span class="n">section</span><span class="o">]</span> <span class="p">:</span> <span class="kp">nil</span><span class="p">;</span>
            <span class="sr">//</span> <span class="err">先初始化一个默认的</span> <span class="n">headerView</span> <span class="err">，如果没有直接设置</span> <span class="n">headerView</span> <span class="err">就直接更换标题</span>
            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">sectionRecord</span><span class="o">.</span><span class="n">headerView</span> <span class="o">&amp;&amp;</span> <span class="n">sectionRecord</span><span class="o">.</span><span class="n">headerHeight</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">sectionRecord</span><span class="o">.</span><span class="n">headerTitle</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">sectionRecord</span><span class="o">.</span><span class="n">headerView</span> <span class="o">=</span> <span class="o">[</span><span class="no">UITableViewSectionLabel</span> <span class="ss">sectionLabelWithTitle</span><span class="p">:</span><span class="n">sectionRecord</span><span class="o">.</span><span class="n">headerTitle</span><span class="o">]</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="sr">//</span> <span class="no">Footer</span> <span class="err">也做相同的处理</span>
            <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">sectionRecord</span><span class="o">.</span><span class="n">footerView</span> <span class="o">&amp;&amp;</span> <span class="n">sectionRecord</span><span class="o">.</span><span class="n">footerHeight</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">sectionRecord</span><span class="o">.</span><span class="n">footerTitle</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">sectionRecord</span><span class="o">.</span><span class="n">footerView</span> <span class="o">=</span> <span class="o">[</span><span class="no">UITableViewSectionLabel</span> <span class="ss">sectionLabelWithTitle</span><span class="p">:</span><span class="n">sectionRecord</span><span class="o">.</span><span class="n">footerTitle</span><span class="o">]</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">sectionRecord</span><span class="o">.</span><span class="n">headerView</span><span class="p">)</span> <span class="p">{</span>
                <span class="o">[</span><span class="nb">self</span> <span class="ss">addSubview</span><span class="p">:</span><span class="n">sectionRecord</span><span class="o">.</span><span class="n">headerView</span><span class="o">]</span><span class="p">;</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="n">sectionRecord</span><span class="o">.</span><span class="n">headerHeight</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">sectionRecord</span><span class="o">.</span><span class="n">footerView</span><span class="p">)</span> <span class="p">{</span>
                <span class="o">[</span><span class="nb">self</span> <span class="ss">addSubview</span><span class="p">:</span><span class="n">sectionRecord</span><span class="o">.</span><span class="n">footerView</span><span class="o">]</span><span class="p">;</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="n">sectionRecord</span><span class="o">.</span><span class="n">footerHeight</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="sr">//</span> <span class="err">为高度数组动态开辟空间</span>
            <span class="no">CGFloat</span> <span class="o">*</span><span class="n">rowHeights</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="n">numberOfRowsInSection</span> <span class="o">*</span> <span class="n">sizeof</span><span class="p">(</span><span class="no">CGFloat</span><span class="p">));</span>
            <span class="sr">//</span> <span class="err">初始化总高度</span>
            <span class="no">CGFloat</span> <span class="n">totalRowsHeight</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
            <span class="k">for</span> <span class="p">(</span><span class="no">NSInteger</span> <span class="n">row</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span> <span class="n">row</span><span class="o">&lt;</span><span class="n">numberOfRowsInSection</span><span class="p">;</span> <span class="n">row</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
                <span class="sr">//</span> <span class="err">获取</span> <span class="no">Cell</span> <span class="err">高度，未设置则使用默认高度</span>
                <span class="n">const</span> <span class="no">CGFloat</span> <span class="n">rowHeight</span> <span class="o">=</span> <span class="n">_delegateHas</span><span class="o">.</span><span class="n">heightForRowAtIndexPath?</span> <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">delegate</span> <span class="ss">tableView</span><span class="p">:</span><span class="nb">self</span> <span class="ss">heightForRowAtIndexPath</span><span class="p">:</span><span class="o">[</span><span class="no">NSIndexPath</span> <span class="ss">indexPathForRow</span><span class="p">:</span><span class="n">row</span> <span class="ss">inSection</span><span class="p">:</span><span class="n">section</span><span class="o">]]</span> <span class="p">:</span> <span class="n">defaultRowHeight</span><span class="p">;</span>
                <span class="sr">//</span> <span class="err">记录高度</span>
                <span class="n">rowHeights</span><span class="o">[</span><span class="n">row</span><span class="o">]</span> <span class="o">=</span> <span class="n">rowHeight</span><span class="p">;</span>
                <span class="sr">//</span> <span class="err">总高度统计</span>
                <span class="n">totalRowsHeight</span> <span class="o">+=</span> <span class="n">rowHeight</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="n">sectionRecord</span><span class="o">.</span><span class="n">rowsHeight</span> <span class="o">=</span> <span class="n">totalRowsHeight</span><span class="p">;</span>
            <span class="o">[</span><span class="n">sectionRecord</span> <span class="ss">setNumberOfRows</span><span class="p">:</span><span class="n">numberOfRowsInSection</span> <span class="ss">withHeights</span><span class="p">:</span><span class="n">rowHeights</span><span class="o">]</span><span class="p">;</span>
            <span class="n">free</span><span class="p">(</span><span class="n">rowHeights</span><span class="p">);</span>
            <span class="sr">//</span> <span class="err">缓存高度记录</span>
            <span class="o">[</span><span class="n">_sections</span> <span class="ss">addObject</span><span class="p">:</span><span class="n">sectionRecord</span><span class="o">]</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p>我们发现在 <code>_updateSectionsCache</code> 更新缓存状态的过程中对 <code>_sections</code> 中的数据全部清除。之后缓存了更新后的所有 Section 数据。那么这些数据有什么利用价值呢？继续来看<strong>布局更新</strong>操作。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">-</span> <span class="p">(</span><span class="n">void</span><span class="p">)</span><span class="n">_layoutTableView</span> <span class="p">{</span>
    <span class="sr">//</span> <span class="err">在需要渲染时放置需要的</span> <span class="no">Header</span> <span class="err">和</span> <span class="no">Cell</span>
    <span class="sr">//</span> <span class="err">缓存所有出现的单元格，并添加至复用容器</span>
    <span class="sr">//</span> <span class="err">之后那些不显示但是已经出现的</span> <span class="no">Cell</span> <span class="err">将会被复用</span>
    <span class="sr">//</span> <span class="err">获取容器视图相对于父类视图的尺寸及坐标</span>
    <span class="n">const</span> <span class="no">CGSize</span> <span class="n">boundsSize</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">bounds</span><span class="o">.</span><span class="n">size</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">获取向下滑动偏移量</span>
    <span class="n">const</span> <span class="no">CGFloat</span> <span class="n">contentOffset</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">contentOffset</span><span class="o">.</span><span class="n">y</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">获取可视矩形框的尺寸</span>
    <span class="n">const</span> <span class="no">CGRect</span> <span class="n">visibleBounds</span> <span class="o">=</span> <span class="no">CGRectMake</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="n">contentOffset</span><span class="p">,</span><span class="n">boundsSize</span><span class="o">.</span><span class="n">width</span><span class="p">,</span><span class="n">boundsSize</span><span class="o">.</span><span class="n">height</span><span class="p">);</span>
    <span class="sr">//</span> <span class="err">表高纪录值</span>
    <span class="no">CGFloat</span> <span class="n">tableHeight</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">如果有</span> <span class="n">header</span> <span class="err">则需要额外计算</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">_tableHeaderView</span><span class="p">)</span> <span class="p">{</span>
        <span class="no">CGRect</span> <span class="n">tableHeaderFrame</span> <span class="o">=</span> <span class="n">_tableHeaderView</span><span class="o">.</span><span class="n">frame</span><span class="p">;</span>
        <span class="n">tableHeaderFrame</span><span class="o">.</span><span class="n">origin</span> <span class="o">=</span> <span class="no">CGPointZero</span><span class="p">;</span>
        <span class="n">tableHeaderFrame</span><span class="o">.</span><span class="n">size</span><span class="o">.</span><span class="n">width</span> <span class="o">=</span> <span class="n">boundsSize</span><span class="o">.</span><span class="n">width</span><span class="p">;</span>
        <span class="n">_tableHeaderView</span><span class="o">.</span><span class="n">frame</span> <span class="o">=</span> <span class="n">tableHeaderFrame</span><span class="p">;</span>
        <span class="n">tableHeight</span> <span class="o">+=</span> <span class="n">tableHeaderFrame</span><span class="o">.</span><span class="n">size</span><span class="o">.</span><span class="n">height</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="sr">//</span> <span class="n">availableCell</span> <span class="err">记录当前正在显示的</span> <span class="no">Cell</span>
    <span class="sr">//</span> <span class="err">在滑出显示区之后将添加至</span> <span class="n">_reusableCells</span>
    <span class="no">NSMutableDictionary</span> <span class="o">*</span><span class="n">availableCells</span> <span class="o">=</span> <span class="o">[</span><span class="n">_cachedCells</span> <span class="n">mutableCopy</span><span class="o">]</span><span class="p">;</span>
    <span class="n">const</span> <span class="no">NSInteger</span> <span class="n">numberOfSections</span> <span class="o">=</span> <span class="o">[</span><span class="n">_sections</span> <span class="n">count</span><span class="o">]</span><span class="p">;</span>
    <span class="o">[</span><span class="n">_cachedCells</span> <span class="n">removeAllObjects</span><span class="o">]</span><span class="p">;</span>
    <span class="sr">//</span> <span class="err">滑动列表，更新当前显示容器</span>
    <span class="k">for</span> <span class="p">(</span><span class="no">NSInteger</span> <span class="n">section</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span> <span class="n">section</span><span class="o">&lt;</span><span class="n">numberOfSections</span><span class="p">;</span> <span class="n">section</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="no">CGRect</span> <span class="n">sectionRect</span> <span class="o">=</span> <span class="o">[</span><span class="nb">self</span> <span class="ss">rectForSection</span><span class="p">:</span><span class="n">section</span><span class="o">]</span><span class="p">;</span>
        <span class="n">tableHeight</span> <span class="o">+=</span> <span class="n">sectionRect</span><span class="o">.</span><span class="n">size</span><span class="o">.</span><span class="n">height</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="no">CGRectIntersectsRect</span><span class="p">(</span><span class="n">sectionRect</span><span class="p">,</span> <span class="n">visibleBounds</span><span class="p">))</span> <span class="p">{</span>
            <span class="n">const</span> <span class="no">CGRect</span> <span class="n">headerRect</span> <span class="o">=</span> <span class="o">[</span><span class="nb">self</span> <span class="ss">rectForHeaderInSection</span><span class="p">:</span><span class="n">section</span><span class="o">]</span><span class="p">;</span>
            <span class="n">const</span> <span class="no">CGRect</span> <span class="n">footerRect</span> <span class="o">=</span> <span class="o">[</span><span class="nb">self</span> <span class="ss">rectForFooterInSection</span><span class="p">:</span><span class="n">section</span><span class="o">]</span><span class="p">;</span>
            <span class="no">UITableViewSection</span> <span class="o">*</span><span class="n">sectionRecord</span> <span class="o">=</span> <span class="o">[</span><span class="n">_sections</span> <span class="ss">objectAtIndex</span><span class="p">:</span><span class="n">section</span><span class="o">]</span><span class="p">;</span>
            <span class="n">const</span> <span class="no">NSInteger</span> <span class="n">numberOfRows</span> <span class="o">=</span> <span class="n">sectionRecord</span><span class="o">.</span><span class="n">numberOfRows</span><span class="p">;</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">sectionRecord</span><span class="o">.</span><span class="n">headerView</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">sectionRecord</span><span class="o">.</span><span class="n">headerView</span><span class="o">.</span><span class="n">frame</span> <span class="o">=</span> <span class="n">headerRect</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="k">if</span> <span class="p">(</span><span class="n">sectionRecord</span><span class="o">.</span><span class="n">footerView</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">sectionRecord</span><span class="o">.</span><span class="n">footerView</span><span class="o">.</span><span class="n">frame</span> <span class="o">=</span> <span class="n">footerRect</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="k">for</span> <span class="p">(</span><span class="no">NSInteger</span> <span class="n">row</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span> <span class="n">row</span><span class="o">&lt;</span><span class="n">numberOfRows</span><span class="p">;</span> <span class="n">row</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
                <span class="sr">//</span> <span class="err">构造</span> <span class="n">indexPath</span> <span class="err">为代理方法准备</span>
                <span class="no">NSIndexPath</span> <span class="o">*</span><span class="n">indexPath</span> <span class="o">=</span> <span class="o">[</span><span class="no">NSIndexPath</span> <span class="ss">indexPathForRow</span><span class="p">:</span><span class="n">row</span> <span class="ss">inSection</span><span class="p">:</span><span class="n">section</span><span class="o">]</span><span class="p">;</span>
                <span class="sr">//</span> <span class="err">获取第</span> <span class="n">row</span> <span class="err">个坐标位置</span>
                <span class="no">CGRect</span> <span class="n">rowRect</span> <span class="o">=</span> <span class="o">[</span><span class="nb">self</span> <span class="ss">rectForRowAtIndexPath</span><span class="p">:</span><span class="n">indexPath</span><span class="o">]</span><span class="p">;</span>
                <span class="sr">//</span> <span class="err">判断当前</span> <span class="no">Cell</span> <span class="err">是否与显示区域相交</span>
                <span class="k">if</span> <span class="p">(</span><span class="no">CGRectIntersectsRect</span><span class="p">(</span><span class="n">rowRect</span><span class="p">,</span><span class="n">visibleBounds</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">rowRect</span><span class="o">.</span><span class="n">size</span><span class="o">.</span><span class="n">height</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
                    <span class="sr">//</span> <span class="err">首先查看</span> <span class="n">availableCells</span> <span class="err">中是否已经有了当前</span> <span class="no">Cell</span> <span class="err">的存储</span>
                    <span class="sr">//</span> <span class="err">如果没有，则请求</span> <span class="n">tableView</span> <span class="err">的代理方法获取</span> <span class="no">Cell</span>
                    <span class="no">UITableViewCell</span> <span class="o">*</span><span class="n">cell</span> <span class="o">=</span> <span class="o">[</span><span class="n">availableCells</span> <span class="ss">objectForKey</span><span class="p">:</span><span class="n">indexPath</span><span class="o">]</span> <span class="sc">?:</span> <span class="o">[</span><span class="nb">self</span><span class="o">.</span><span class="n">dataSource</span> <span class="ss">tableView</span><span class="p">:</span><span class="nb">self</span> <span class="ss">cellForRowAtIndexPath</span><span class="p">:</span><span class="n">indexPath</span><span class="o">]</span><span class="p">;</span>
                    <span class="sr">//</span> <span class="err">由于碰撞检测生效，则按照逻辑需要更新</span> <span class="n">availableCells</span> <span class="err">字典</span>
                    <span class="k">if</span> <span class="p">(</span><span class="n">cell</span><span class="p">)</span> <span class="p">{</span>
                        <span class="sr">//</span> <span class="err">获取到</span> <span class="no">Cell</span> <span class="err">后，将其进行缓存操作</span>
                        <span class="o">[</span><span class="n">_cachedCells</span> <span class="ss">setObject</span><span class="p">:</span><span class="n">cell</span> <span class="ss">forKey</span><span class="p">:</span><span class="n">indexPath</span><span class="o">]</span><span class="p">;</span>
                        <span class="o">[</span><span class="n">availableCells</span> <span class="ss">removeObjectForKey</span><span class="p">:</span><span class="n">indexPath</span><span class="o">]</span><span class="p">;</span>
                        <span class="n">cell</span><span class="o">.</span><span class="n">highlighted</span> <span class="o">=</span> <span class="o">[</span><span class="n">_highlightedRow</span> <span class="ss">isEqual</span><span class="p">:</span><span class="n">indexPath</span><span class="o">]</span><span class="p">;</span>
                        <span class="n">cell</span><span class="o">.</span><span class="n">selected</span> <span class="o">=</span> <span class="o">[</span><span class="n">_selectedRow</span> <span class="ss">isEqual</span><span class="p">:</span><span class="n">indexPath</span><span class="o">]</span><span class="p">;</span>
                        <span class="n">cell</span><span class="o">.</span><span class="n">frame</span> <span class="o">=</span> <span class="n">rowRect</span><span class="p">;</span>
                        <span class="n">cell</span><span class="o">.</span><span class="n">backgroundColor</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">backgroundColor</span><span class="p">;</span>
                        <span class="o">[</span><span class="n">cell</span> <span class="ss">_setSeparatorStyle</span><span class="p">:</span><span class="n">_separatorStyle</span> <span class="ss">color</span><span class="p">:</span><span class="n">_separatorColor</span><span class="o">]</span><span class="p">;</span>
                        <span class="o">[</span><span class="nb">self</span> <span class="ss">addSubview</span><span class="p">:</span><span class="n">cell</span><span class="o">]</span><span class="p">;</span>
                    <span class="p">}</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="sr">//</span> <span class="err">将已经退出屏幕且定义</span> <span class="n">reuseIdentifier</span> <span class="err">的</span> <span class="no">Cell</span> <span class="err">加入可复用</span> <span class="no">Cell</span> <span class="err">容器中</span>
    <span class="k">for</span> <span class="p">(</span><span class="no">UITableViewCell</span> <span class="o">*</span><span class="n">cell</span> <span class="k">in</span> <span class="o">[</span><span class="n">availableCells</span> <span class="n">allValues</span><span class="o">]</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">cell</span><span class="o">.</span><span class="n">reuseIdentifier</span><span class="p">)</span> <span class="p">{</span>
            <span class="o">[</span><span class="n">_reusableCells</span> <span class="ss">addObject</span><span class="p">:</span><span class="n">cell</span><span class="o">]</span><span class="p">;</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="o">[</span><span class="n">cell</span> <span class="n">removeFromSuperview</span><span class="o">]</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="sr">//</span> <span class="err">不能复用的</span> <span class="no">Cell</span> <span class="err">会直接销毁，可复用的</span> <span class="no">Cell</span> <span class="err">会存储在</span> <span class="n">_reusableCells</span>
    <span class="sr">//</span> <span class="err">确保所有的可用（未出现在屏幕上）的复用单元格在</span> <span class="n">availableCells</span> <span class="err">中</span>
    <span class="sr">//</span> <span class="err">这样缓存的目的之一是确保动画的流畅性。在动画的帧上都会对显示部分进行处理，重新计算可见</span> <span class="no">Cell</span><span class="err">。</span>
    <span class="sr">//</span> <span class="err">如果直接删除掉所有未出现在屏幕上的单元格，在视觉上会观察到突然消失的动作</span>
    <span class="sr">//</span> <span class="err">整体动画具有跳跃性而显得不流畅</span>
    <span class="sr">//</span> <span class="err">把在可视区的</span> <span class="no">Cell</span><span class="err">（但不在屏幕上）已经被回收为可复用的</span> <span class="no">Cell</span> <span class="err">从视图中移除</span>
    <span class="no">NSArray</span><span class="o">*</span> <span class="n">allCachedCells</span> <span class="o">=</span> <span class="o">[</span><span class="n">_cachedCells</span> <span class="n">allValues</span><span class="o">]</span><span class="p">;</span>
    <span class="k">for</span> <span class="p">(</span><span class="no">UITableViewCell</span> <span class="o">*</span><span class="n">cell</span> <span class="k">in</span> <span class="n">_reusableCells</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="no">CGRectIntersectsRect</span><span class="p">(</span><span class="n">cell</span><span class="o">.</span><span class="n">frame</span><span class="p">,</span><span class="n">visibleBounds</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="o">![</span><span class="n">allCachedCells</span> <span class="ss">containsObject</span><span class="p">:</span> <span class="n">cell</span><span class="o">]</span><span class="p">)</span> <span class="p">{</span>
            <span class="o">[</span><span class="n">cell</span> <span class="n">removeFromSuperview</span><span class="o">]</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">_tableFooterView</span><span class="p">)</span> <span class="p">{</span>
        <span class="no">CGRect</span> <span class="n">tableFooterFrame</span> <span class="o">=</span> <span class="n">_tableFooterView</span><span class="o">.</span><span class="n">frame</span><span class="p">;</span>
        <span class="n">tableFooterFrame</span><span class="o">.</span><span class="n">origin</span> <span class="o">=</span> <span class="no">CGPointMake</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="n">tableHeight</span><span class="p">);</span>
        <span class="n">tableFooterFrame</span><span class="o">.</span><span class="n">size</span><span class="o">.</span><span class="n">width</span> <span class="o">=</span> <span class="n">boundsSize</span><span class="o">.</span><span class="n">width</span><span class="p">;</span>
        <span class="n">_tableFooterView</span><span class="o">.</span><span class="n">frame</span> <span class="o">=</span> <span class="n">tableFooterFrame</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></div>
<p><code>CGRectIntersectsRect</code> 方法用于检测两个 Rect 的碰撞情况。如下图所示：</p>
<p><img src="http://7xwh85.com1.z0.glb.clouddn.com/Mobile%20Portrait%20Copy.png" alt="Mobile Portrait Copy" /></p>
<p>如果你已经对 <code>UITableView</code> 的缓存机制有所了解，那么你在阅读完代码之后会对其有更深刻的认识。如果看完代码还是一头雾水，那么请继续看下面的分析。</p>
<h2 id="cell-">Cell 复用场景三个阶段</h2>
<h3 id="section-1">布局方法触发阶段</h3>
<p>在用户触摸屏幕后，硬件报告触摸时间传递至 <code>UIKit</code> 框架，之后 <code>UIKit</code> 将触摸事件打包成 <code>UIEvent</code> 对象，分发至指定视图。这时候其视图就会做出相应，并调用 <code>setNeedsLayout</code> 方法告诉视图及其子视图需要进行布局更新。此时，<code>setNeedsLayout</code> 被调用，也就变为 Cell 复用场景的入口。</p>
<p><img src="http://7xwh85.com1.z0.glb.clouddn.com/Mobile%20Landscape%20Copy.png" alt="布局方法调用栈" /></p>
<h3 id="cell--1">缓存 Cell 高度信息阶段</h3>
<p>当视图加载后，由 <code>UIKit</code> 调用布局方法 <code>layoutSubviews</code> 从而进入<strong>缓存 Cell 高度阶段</strong> <code>_updateSectionsCache</code>。在这个阶段，通过代理方法 <code>heightForRowAtIndexPath:</code> 获取每一个 Cell 的高度，并将高度信息缓存起来。这其中的高度信息由 <code>UITableViewSection</code> 的一个实例 <code>sectionRecord</code> 进行存储，其中以 section 为单位，存储每个 section 中各个 Cell 的高度、Cell 的数量、以及 section 的总高度、footer 和 header 高度这些信息。这一部分的信息采集是为了在 Cell 复用的核心部分，Cell 的 Rect 尺寸与 tableView 尺寸计算边界情况建立数据基础。</p>
<p><img src="http://7xwh85.com1.z0.glb.clouddn.com/Mobile%20Landscape%20Copy%202.png" alt="_sections 结构示意图" /></p>
<h3 id="cell--2">复用 Cell 的核心处理阶段</h3>
<p>我们要关注三个存储容器的变化情况：</p>
<ul>
  <li><code>NSMutableDictionary</code> 类型 <strong>_cachedCells</strong>：用来存储当前屏幕上所有 Cell 与其对应的 indexPath。以键值对的关系进行存储。</li>
  <li><code>NSMutableDictionary</code> 类型 <strong>availableCells</strong>：当列表发生滑动的时候，部分 Cell 从屏幕移出，这个容器会对 <code>_cachedCells</code> 进行拷贝，然后将屏幕上此时的 Cell 全部去除。即最终取出所有退出屏幕的 Cell。</li>
  <li><code>NSMutableSet</code> 类型 <strong>_reusableCells</strong>：用来收集曾经出现过此时未出现在屏幕上的 Cell。当再出滑入主屏幕时，则直接使用其中的对象根据 <code>CGRectIntersectsRect</code> Rect 碰撞试验进行复用。</li>
</ul>
<p>在整个核心复用阶段，这三个容器都充当着很重要的角色。我们给出以下的场景实例，例如下图的一个场景，图 ① 为页面刚刚载入的阶段，图 ② 为用户向下滑动一个单元格时的状态：</p>
<p><img src="http://7xwh85.com1.z0.glb.clouddn.com/Tablet%209%E2%80%B3%20Landscape.png" alt="核心处理阶段容器变化" /></p>
<p>当到状态 ② 的时候，我们发现 <code>_reusableCells</code> 容器中，已经出现了状态 ① 中已经退出屏幕的 Cell 0。而当我们重新将 Cell 0 滑入界面的时候，在系统 <code>addView</code> 渲染阶段，会直接将 <code>_reusableCells</code> 中的 Cell 0 立即取出进行渲染，从而代替创建新的实例再进行渲染，简化了时间与性能上的开销。</p>
<h2 id="uitableview--1">UITableView 的其他细节优化</h2>
<h3 id="nsmutableset">复用容器数据类型 <code>NSMutableSet</code></h3>
<p>在三个重要的容器中，只有 <code>_reusableCells</code> 使用了 <code>NSMutableSet</code>。这是因为我们在每一次对于 <code>_cachedCells</code> 中的 Cell 进行遍历并在屏幕上渲染时，都需要在 <code>_reusableCells</code> 进行一次扫描。而且当一个页面反复的上下滑动时，<code>_reusableCells</code> 的检索复杂度是相当庞大的。为了确保这一情况下滑动的流畅性，Apple 在设计时不得不将检索复杂度最小化。并且这个复杂度要是非抖动的，不能给体验造成太大的不稳定性。</p>
<p>在 C++ 的 STL 标准库中也有 <code>multiset</code> 数据类型，其中实现的方法是通过构建<strong>红黑树</strong>来实现。因为红黑树具有高效检索的性质，这也是 <code>set</code> 的一个普遍特点。也许是 <code>NSMutableSet</code> 是 <em>Foundation</em> 框架的数据结构，构造其主要目的是为了更快的检索。所以 <code>NSMutableSet</code> 的实现并没有使用红黑树，而是暴力的使用 <strong>Hash 表</strong>实现。从 <em>Core Foundation</em> 中的 <a href="https://opensource.apple.com/source/CF/CF-1151.16/CFSet.c.auto.html">CFSet.c</a> 可以清晰的看见其底层实现。在很久之前的 <a href="https://lists.apple.com/archives/Cocoa-dev/2004/Feb/msg01658.html">Cocoa Dev</a> 的提问帖中也能发现答案。</p>
<h3 id="sections">高度缓存容器 _sections</h3>
<p>在每次布局方法触发阶段，由于 Cell 的状态发生了变化。在对 Cell 复用容器的修改之前，首先要做的一件事是以 Section 为单位对所有的 Cell 进行缓存高度。从这里可以看出 <code>UITableView</code> 设计师的细节。 Cell 的高度在 <code>UITableView</code> 中充当着十分重要的角色，一下列表是需要使用高度的方法：</p>
<ul>
  <li><code>- (CGFloat)_offsetForSection:(NSInteger)index</code>：计算指定 Cell 的滑动偏移量。</li>
  <li><code>- (CGRect)rectForSection:(NSInteger)section</code>：返回某个 Section 的整体 Rect。</li>
  <li><code>- (CGRect)rectForHeaderInSection:(NSInteger)section</code>：返回某个 Header 的 Rect。</li>
  <li><code>- (CGRect)rectForFooterInSection:(NSInteger)section</code>：返回某个 Footer 的 Rect。</li>
  <li><code>- (CGRect)rectForRowAtIndexPath:(NSIndexPath *)indexPath</code>：返回某个 Cell 的 Rect。</li>
  <li><code>- (NSArray *)indexPathsForRowsInRect:(CGRect)rect</code>：返回 Rect 列表。</li>
  <li><code>- (void)_setContentSize</code>：根据高度计算 <code>UITableView</code> 中实际内容的 Size。</li>
</ul>
<h2 id="section-2">一次有拓展性的源码研究</h2>
<p>在阅读完 Chameleon 工程中的 <code>UITableView</code> 源码，进一步可以去查看 <code>FDTemplateLayoutCell</code> 的优化方案。Apple 的工程师对于细节的处理和方案值得各位开发者细细寻味。多探求、多阅读以写出更优雅的代码。😄</p>
  <footer class="post-footer">
    <section class="author">
      <h4>Desgard_Duan</h4>
      <p>I write many code to write less code.💻</p>
    </section>
<aside class="share">
  <h4>Share this.</h4>
  <a href="http://twitter.com/share?text=《复用的精妙 - UITableView 复用技术原理分析》 -- Guardia · 瓜地&amp;url=https://www.desgard.com/TableView-Reuse/"
  onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-twitter-square"></i>
  </a>
  <a href="http://v.t.sina.com.cn/share/share.php?url=https://www.desgard.com/TableView-Reuse/&amp;title=《复用的精妙 - UITableView 复用技术原理分析》 —— Guardia · 瓜地" onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-weibo"></i>
  </a>
</aside>
      <hr>
<aside id="comments" class="disqus">
  <div id="container"></div>
  <!-- <link rel="stylesheet" href="/assets/css/gitment.css">
  <script src="https://imsun.github.io/gitment/dist/gitment.browser.js"></script>
  <script>
  var gitment = new Gitment({
    id: "https://www.desgard.com/TableView-Reuse/", 
    owner: 'Desgard',
    repo: 'desgard.github.com',
    oauth: {
      client_id: 'e2612df42f3f2a83e71c',
      client_secret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
    },
  })
  gitment.render('container')
  </script> -->
<link rel="stylesheet" href="/assets/css/gitalk.css">
<script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>
<div id='gitalk-container'></div>
<script>
    const gitalk = new Gitalk({
        id: "https://www.desgard.com/TableView-Reuse/", 
        clientID: 'e2612df42f3f2a83e71c',
        clientSecret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
        repo: 'desgard.github.com',
        owner: 'Desgard',
        admin: ['Desgard'],
        // facebook-like distraction free mode
        distractionFreeMode: false
    })
    gitalk.render('gitalk-container')
</script>
</aside>
  </footer>
</article>
    </main>
<footer class="footer">
  <div class="container">
    <ul class="icons">
      <li>
        <a href="https://github.com/desgard" class="icon-github" target="_blank">
          <i class="fa fa-github"></i>
        </a>
      </li>
      <li>
        <a href="https://www.facebook.com/desgard.duan" class="icon-facebook" target="_blank">
          <i class="fa fa-facebook"></i>
        </a>
      </li>
      <li>
        <a href="https://twitter.com/Desgard_Duan" class="icon-twitter" target="_blank">
          <i class="fa fa-twitter"></i>
        </a>
      </li>
      <li>
        <a href="https://stackoverflow.com/users/6119149/desgard-duan" class="icon-github" target="_blank">
          <i class="fa fa-stack-overflow"></i>
        </a>
      </li>
      <li>
        <a href="https://weibo.com/desgard" class="icon-instagram" target="_blank">
          <i class="fa fa-weibo"></i>
        </a>
      </li>
    </ul>
    <p>
      <q>I write many code to write less code.💻</q>
      <small>– Gua</small>
    </p>
    <small class="clearfix">
      Powered by <a href="http://jekyllrb.com" target="_blank">Jekyll</a> • <a href="https://github.com/desgard" target="_blank">Open source <i class="fa fa-heart"></i></a>
    </small>
  </div>
</footer>
<a class="scroll-up fa fa-chevron-up bounce" href="#" data-position="0"></a>
<div id="modalSearch" class="modal">
  <div class="modal__overlay"></div>
  <div class="modal__content">
    <a href="#!" class="modal-close" data-modal-close>&times;</a>
    <div class="search-container">
      <input type="text" id="search-input" placeholder="Search articles">
      <ul id="results-container"></ul>
    </div>
  </div>
</div>
    <script src="/assets/main-52d417e8a6ff9f5b168386d37c96338a.js"></script>
  </body>
  <script>
    var link = "" ;
    var os = function() {  
      var ua = navigator.userAgent,  
      isWindowsPhone = /(?:Windows Phone)/.test(ua),  
      isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone,   
      isAndroid = /(?:Android)/.test(ua),   
      isFireFox = /(?:Firefox)/.test(ua),   
      isChrome = /(?:Chrome|CriOS)/.test(ua),  
      isTablet = /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua)),  
      isPhone = /(?:iPhone)/.test(ua) && !isTablet,  
      isPc = !isPhone && !isAndroid && !isSymbian;  
      return {  
        isTablet: isTablet,  
        isPhone: isPhone,  
        isAndroid : isAndroid,  
        isPc : isPc  
      };  
    }();  
    if (link.length > 0) {
      if (os.isAndroid || os.isPhone) {
        location.replace(link);
      }
    }
  </script>
</html>